/** 不等高*/

import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import img1 from '@/assets/images/logo.png';
import './index.less';

interface Sign {
  top: number;
  height: number;
  bottom: number;
}

/** 查找第一个小于等于的值*/

/**
 * @description 查找第一个小于等于的num的下标
 * @param l 开始下标
 * @param r 结束下标
 * @param list 所查询列表
 * @param num 目标值
 * @returns 下标
 */
function dichotomy(l: number, r: number, list: Sign[], num: number) {
  while (l <= r) {
    let mid = Math.floor((l + r) / 2);
    if (list[mid].top <= num) {
      l = mid + 1;
    } else {
      r = mid - 1;
    }
  }
  return r;
}

export default function VirtualScrollOn() {
  /** 默认高度*/
  const [listItemDefaultHeight, setListItemDefaultHeight] = useState(50);
  /** 滑块DOM*/
  const scrollBar = useRef<any>(null);
  /** 滑块样式*/
  const [scrollBarStyle, setScrollBarStyle] = useState<React.CSSProperties>({});
  /** 列表样式*/
  const [listStyle, setListStyle] = useState<React.CSSProperties>({});
  /** 滑块是否可以移动*/
  const isMove = useRef(false);
  /** 列表在视口的坐标*/
  const initPointerObj = useRef({ x: 0, y: 0 });
  /** 鼠标点击时在滑块中的坐标*/
  const mouseInBar = useRef({ x: 0, y: 0 });
  const frameDom = useRef(null);
  /** 列表可渲染数量*/
  const renderQuantity = useRef(20);
  /** 列表DOM*/
  const listDom = useRef(null);
  /** 列表可见区域高度*/
  const listVisualHeight = useRef(0);
  /** 数据开始下标*/
  const [startInd, setStartInd] = useState(0);
  /** 总数据*/
  const [allList, setAllList] = useState<any[]>([]);
  /** 渲染数据*/
  const [resultList, setResultList] = useState<any[]>([]);
  /** 标记*/
  const [sign, setSign] = useState<Sign[]>([]);

  /** 列表数量*/
  const listNum = useMemo(() => {
    return allList.length;
  }, [allList]);

  /** 列表实际高度*/
  const listActualHeight = useMemo(() => {
    if (listNum == 0 || sign.length == 0) {
      return 0;
    }
    return sign[listNum - 1].bottom;
  }, [listNum, sign]);

  /** 滑块高度*/
  const barHeight = useMemo(() => {
    let height =
      (listVisualHeight.current / listActualHeight) * listVisualHeight.current;
    if (height < 20) {
      return height * (Math.floor(20 / height) + 1);
    } else {
      return height;
    }
  }, [listActualHeight]);

  /** 滑块最大可滚动距离*/
  const maxBarTransformY = useMemo(() => {
    if (scrollBar.current === null) {
      return listVisualHeight.current;
    }
    return listVisualHeight.current - barHeight;
  }, [barHeight]);

  /** 更新dom高度信息*/
  const updateSign = useCallback(() => {
    let childNodes = (listDom.current as unknown as HTMLDivElement).childNodes;
    let temporarySign = [...sign];

    if (resultList.length === 0) {
      return;
    }

    /** 计算实际渲染部分与记录部分的总差值，用于同一计算*/
    let addSubSum = 0;
    for (let i = 0; i < childNodes.length && startInd + i < listNum; i++) {
      let itemInfo = (childNodes[i] as HTMLDivElement).getBoundingClientRect();
      addSubSum += itemInfo.height - temporarySign[startInd + i].height;
    }

    //更新 渲染元素高度信息
    for (let i = 0; i < childNodes.length && startInd + i < listNum; i++) {
      let itemInfo = (childNodes[i] as HTMLDivElement).getBoundingClientRect();
      if (itemInfo.height === temporarySign[startInd + i].height) {
        continue;
      } else {
        if (startInd + i === 0) {
          temporarySign[i] = {
            height: itemInfo.height,
            top: 0,
            bottom: itemInfo.height,
          };
        } else {
          temporarySign[startInd + i] = {
            height: itemInfo.height,
            top: temporarySign[startInd + i - 1].bottom,
            bottom: temporarySign[startInd + i - 1].bottom + itemInfo.height,
          };
        }
      }
    }

    //更新 渲染元素之后的元素高度信息
    if (addSubSum != 0) {
      for (let i = startInd + childNodes.length; i < listNum; i++) {
        temporarySign[i] = {
          ...temporarySign[i],
          top: temporarySign[i].top + addSubSum,
          bottom: temporarySign[i].bottom + addSubSum,
        };
      }
    }

    setSign(temporarySign);
  }, [startInd, listNum, sign, resultList]);

  /** 获取数据,初始化数据*/
  useEffect(() => {
    let list = [];
    for (let i = 0; i < 10000; i++) {
      let content = `我是第${i}张图`;
      for (let j = 0; j < Math.floor(Math.random() * (7 - 3 + 1)) + 3; j++) {
        content += `${1 * 100000000000}`;
      }
      list.push({
        img: img1,
        content: content,
      });
    }
    setAllList(list);

    //初始化列表元素高度信息
    let temporarySign: Sign[] = [];
    for (let i = 0; i < list.length; i++) {
      if (i === 0) {
        temporarySign[i] = {
          height: listItemDefaultHeight,
          top: 0,
          bottom: listItemDefaultHeight,
        };
      } else {
        let detail: Sign = {
          height: listItemDefaultHeight,
          top: temporarySign[i - 1].bottom,
          bottom: temporarySign[i - 1].bottom + listItemDefaultHeight,
        };
        temporarySign[i] = detail;
      }
    }

    setSign([...temporarySign]);
  }, [listItemDefaultHeight]);

  /** 初始化渲染数据*/
  useEffect(() => {
    let list = [];
    for (
      let i = startInd;
      i < startInd + renderQuantity.current && i < allList.length;
      i++
    ) {
      list.push(allList[i]);
    }
    setResultList(list);
  }, [allList, startInd]);

  useEffect(() => {
    updateSign();
  }, [resultList]);

  /** 设置鼠标在滑块中点击的y轴坐标*/
  const onMousedown = useCallback((e: MouseEvent) => {
    e.preventDefault();
    document.documentElement.style.cursor = 'grabbing';
    mouseInBar.current.y =
      e.y - (scrollBar.current as HTMLDivElement).getBoundingClientRect().y;
    isMove.current = true;
  }, []);

  const onMouseup = useCallback(() => {
    isMove.current = false;
    document.documentElement.style.cursor = 'default';
  }, []);

  const onMousemove = useCallback(
    (e: MouseEvent) => {
      if (isMove.current === false) {
        return;
      }

      /** 滚动条移动距离*/
      let barTransformY = e.y - initPointerObj.current.y - mouseInBar.current.y;

      if (barTransformY > maxBarTransformY) {
        barTransformY = maxBarTransformY;
      }
      if (barTransformY <= 0) {
        barTransformY = 0;
      }

      /** 实际内容移动距离*/
      let listTransformY =
        (barTransformY * (listActualHeight - listVisualHeight.current)) /
        maxBarTransformY;

      let showInd = dichotomy(0, listNum - 1, sign, listTransformY);

      setScrollBarStyle({
        transform: `translate(${0}px,${barTransformY}px)`,
      });
      setListStyle({
        transform: `translate(${0}px,-${listTransformY - sign[showInd].top}px)`,
      });

      setStartInd(showInd);
    },
    [maxBarTransformY, startInd, listItemDefaultHeight, sign, listNum],
  );

  const onWheel = useCallback(
    (e: WheelEvent) => {
      e.preventDefault();

      let transformValue = window
        .getComputedStyle(listDom.current as unknown as HTMLDivElement)
        .getPropertyValue('transform');
      let translateYValue = 0;
      if (transformValue !== 'none') {
        const matrixValues = (transformValue as any)
          .match(/matrix.*\((.+)\)/)[1]
          .split(', ');
        translateYValue = parseInt(matrixValues[5], 10);
      }
      let listTransformY = sign[startInd].top + Math.abs(translateYValue);

      /** 判断是向上还是向下滚动*/
      const deltaY = e.deltaY;
      if (deltaY > 0) {
        listTransformY = listTransformY + 70;
        if (listTransformY > listActualHeight - listVisualHeight.current) {
          listTransformY = listActualHeight - listVisualHeight.current;
        }
      } else if (deltaY < 0) {
        listTransformY = listTransformY - 70;
        if (listTransformY < 0) {
          listTransformY = 0;
        }
      }

      let barTransformY =
        (listTransformY * maxBarTransformY) /
        (listActualHeight - listVisualHeight.current);
      let showInd = dichotomy(0, listNum - 1, sign, listTransformY);

      setScrollBarStyle({
        transform: `translate(${0}px,${barTransformY}px)`,
      });
      setListStyle({
        transform: `translate(${0}px,-${listTransformY - sign[showInd].top}px)`,
      });

      setStartInd(showInd);
    },
    [startInd, listActualHeight, listItemDefaultHeight, listNum, sign],
  );

  /** 设置初始化滑块在页面中的y轴坐标*/
  const setInitPointer = useCallback(() => {
    if (frameDom.current === null) {
      return;
    }
    let frameDomInfo = (
      frameDom.current as HTMLDivElement
    ).getBoundingClientRect();
    initPointerObj.current.y = frameDomInfo.y;
  }, []);

  /** 初始化数据*/
  useEffect(() => {
    if (frameDom.current === null) {
      return;
    }
    setInitPointer();

    let frameDomInfo = (
      frameDom.current as HTMLDivElement
    ).getBoundingClientRect();
    listVisualHeight.current = frameDomInfo.height;

    (scrollBar.current as HTMLDivElement).addEventListener(
      'mousedown',
      onMousedown,
    );
    window.addEventListener('resize', setInitPointer);
    window.addEventListener('scroll', setInitPointer);
    document.addEventListener('mouseup', onMouseup);

    return () => {
      scrollBar.current &&
        (scrollBar.current as HTMLDivElement).removeEventListener(
          'mousedown',
          onMousedown,
        );
      document.removeEventListener('mouseup', onMouseup);
      window.removeEventListener('resize', setInitPointer);
      window.removeEventListener('scroll', setInitPointer);
    };
  }, []);

  useEffect(() => {
    if (frameDom.current === null) {
      return;
    }
    document.addEventListener('mousemove', onMousemove);
    (frameDom.current as HTMLDivElement).addEventListener('wheel', onWheel);

    return () => {
      frameDom.current &&
        (frameDom.current as unknown as HTMLDivElement).removeEventListener(
          'wheel',
          onWheel,
        );
      document.removeEventListener('mousemove', onMousemove);
    };
  }, [onWheel, onMousemove]);

  return (
    <>
      <div ref={frameDom} className="virtualScroll">
        <div
          ref={listDom}
          className="virtualScroll__list"
          style={{
            ...listStyle,
            height: listActualHeight + 'px',
          }}
        >
          {resultList.map((item, ind) => {
            return (
              <div className="list__item" key={ind}>
                <img src={item.img} alt="" />
                <div>{item.content}</div>
              </div>
            );
          })}
        </div>
        <div
          ref={scrollBar}
          draggable={false}
          style={{
            ...scrollBarStyle,
            height: `${barHeight}px`,
          }}
          className="virtualScroll__scrollBar"
        ></div>
      </div>
    </>
  );
}
